from fastapi import FastAPI, UploadFile, File, HTTPException, Body
from fastapi.responses import JSONResponse
from typing import List, Optional, Dict, Any
from pydantic import BaseModel
import re
import uvicorn
import os
import logging
import yaml
import uuid
from pathlib import Path
from knowledge_base_list_datasetid import create_upload_files
from llm_query_rewrite.rewrite_doubt_by_llm import rewrite_doubt_by_llm
from file_parse.pp_ocrv5_url import ocr_recognize_picture_by_url
# from preliminary_review_report.review_judgement_langgraph import process_file, visualize_graph
from preliminary_review_report.preliminary_review_file_name_judgement import file_name_judgement
from call_llm import _run_all_configs_once
from research_judgement.judgement_clue_repeat import judgement_clue_repeat
import json
# 日志级别
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)

app = FastAPI()

# 加载同目录下的 config.yaml（若存在）
CONFIG = {}
try:
    current_dir = os.path.dirname(__file__)
    config_path = os.path.join(current_dir, "config.yaml")
    if os.path.exists(config_path):
        with open(config_path, "r", encoding="utf-8") as f:
            CONFIG = yaml.safe_load(f) or {}
            if not isinstance(CONFIG, dict):
                CONFIG = {}
except Exception as _config_exc:
    log.warning(f"加载 config.yaml 失败：{_config_exc}")

api_url = CONFIG.get("KNOWLEDGE_BASES_API_URL")
api_key = CONFIG.get("KNOWLEDGE_BASES_API_KEY", "")

# method_1：查询多个知识库合并结果
@app.post("/knowledge_bases_retrieve/")
async def create_upload_files_main(
        knowledge_base_list: str,
        query: str,
        top_k: Optional[int] = 2,
        score_threshold: Optional[float] = 0.1,
        query_rewrite: Optional[str] = "false",
        concurrent: Optional[int] = 1,
        system_prompt: Optional[str] = None,
        disable_delay: Optional[str] = "false",
        content_max_length: Optional[int] = 7000
):
    return await create_upload_files(knowledge_base_list, query, top_k, score_threshold, query_rewrite, concurrent, system_prompt, disable_delay, content_max_length)

# method_2：遍历集合LLM进行改写
@app.post("/rewrite_doubt_by_llm/")
async def rewrite_doubt_by_llm_main(
        # 传入一个用|分隔的字符串
        doubt_list: Optional[str],  # 接收字符串（用|分隔）
        concurrency_limit: int = 5,
        name: Optional[str] = None,
        system_prompt: Optional[str] = None,
        user_prompt: Optional[str] = None,  # 这个暂时用不上的
        concurrent: Optional[int] = None,
):
    return await rewrite_doubt_by_llm(doubt_list.split("|"), concurrency_limit, name, system_prompt, user_prompt, concurrent)

# method_3：PP-OCRv5识别图片（url）
@app.post("/ocr_recognize_piture_by_url/")
async def ocr_recognize_piture_by_url_main(
        # 图片的url地址
        picture_url: Optional[str]
):
    return await ocr_recognize_picture_by_url(picture_url)
    # return None

# # method_4：文件上传接口，使用LangGraph处理
# @app.post("/research_judgement_review/")
# async def preliminary_review_main(
#         file_data: Dict[str, Any] = Body(...)
# ):
#     """
#     上传文件并进行初步审查报告生成
#     接收文件字典，包含文件名、文件内容等信息
#
#     请求体示例:
#     {
#         "filename": "example.pdf",
#         "content": "文件内容字符串",
#         "content_type": "application/pdf",  # 可选
#         "llm_name": "qwen3-14b",  # 可选，默认 "qwen3-14b"
#         "system_prompt": "你是一个专业的文档分析助手...",  # 可选
#         "user_prompt": "请分析以下文档内容：\n\n{parsed_text}",  # 可选，可使用{parsed_text}占位符
#         "concurrent": 1  # 可选，默认 1
#     }
#
#     Args:
#         file_data: 文件字典，包含：
#             - filename: 文件名（必需）
#             - content: 文件内容字符串（必需）
#             - content_type: 文件MIME类型（可选）
#             - llm_name: 大模型名称（可选）
#             - system_prompt: 系统提示词（可选）
#             - user_prompt: 用户提示词（可选，可使用{parsed_text}占位符）
#             - concurrent: 并发数（可选）
#
#     Returns:
#         JSONResponse: 包含处理结果的响应
#     """
#     # 使用当前目录下的 tmp 文件夹
#     current_dir = os.path.dirname(__file__)
#     tmp_dir = Path(current_dir) / "tmp"
#     tmp_dir.mkdir(exist_ok=True)
#
#     try:
#         # 提取文件信息
#         filename = file_data.get("filename")
#         if not filename:
#             raise HTTPException(status_code=400, detail="缺少必需参数: filename")
#
#         content = file_data.get("content")
#         if not content:
#             raise HTTPException(status_code=400, detail="缺少必需参数: content")
#
#         # 获取文件扩展名
#         file_ext = Path(filename).suffix.lower()
#
#         # 生成唯一文件名
#         file_id = str(uuid.uuid4())
#         input_file_path = tmp_dir / f"{file_id}{file_ext}"
#
#         # 处理文件内容：直接将字符串转换为字节
#         if isinstance(content, str):
#             file_content = content.encode('utf-8')
#         elif isinstance(content, bytes):
#             file_content = content
#         else:
#             # 其他类型，转换为字符串再编码
#             file_content = str(content).encode('utf-8')
#
#         # 保存文件
#         with open(input_file_path, "wb") as buffer:
#             buffer.write(file_content)
#
#         # 提取LLM相关参数（可选）
#         llm_name = file_data.get("llm_name")
#         llm_name2 = file_data.get("llm_name2")
#         system_prompt = file_data.get("system_prompt")
#         system_prompt2 = file_data.get("system_prompt2")
#         user_prompt = file_data.get("user_prompt")
#         user_prompt2 = file_data.get("user_prompt2")
#         concurrent = file_data.get("concurrent")
#
#         # 调用LangGraph处理文件
#         result = process_file(
#             str(input_file_path),
#             llm_name=llm_name,
#             llm_name2=llm_name2,
#             system_prompt=system_prompt,
#             system_prompt2=system_prompt2,
#             user_prompt=user_prompt,
#             user_prompt2=user_prompt2,
#             concurrent=concurrent
#         )
#
#         # 检查是否有错误
#         if result.get("error"):
#             raise HTTPException(status_code=500, detail=result["error"])
#
#         # 返回结果
#         return JSONResponse(content={
#             "status": "success",
#             "file_type": result.get("file_type", "unknown"),
#             "parsed_text_length": len(result.get("parsed_text", "")),
#             "llm_result": result.get("llm_result", ""),
#             "llm_result2": result.get("llm_result2", ""),
#             "message": "文件处理完成"
#         })
#
#     except HTTPException:
#         raise
#     except Exception as e:
#         log.error(f"文件处理失败: {e}", exc_info=True)
#         raise HTTPException(status_code=500, detail=f"文件处理失败: {str(e)}")
#     finally:
#         # 清理临时文件（可选，根据需要决定是否删除）
#         # if input_file_path.exists():
#         #     input_file_path.unlink()
#         pass

# 定义请求模型
# class FileNameJudgementRequest(BaseModel):
#     """文件名判断请求模型"""
#     filenames: List[str]  # 文件名列表
#     file_contents: List[str]  # 文件内容列表
#     llm_flag: str = "false"  # 是否启用大模型判断文件类型


# method_5：初核报告，履历，线索核查组底稿，其他（大模型判断文件）
@app.post("/file_name_judgement/")
async def file_name_judgement_main(
        filenames: List[str] = Body(...),
        file_contents: List[str] = Body(...),
        llm_flag: str = Body("false"),
        llm_summarize_flag: str = Body("false"),
        length: int = Body(1000),
        delay: int = Body(100)
):
    """
    根据文件名过滤文件，返回按类型分组的文件内容字典
    情况1：文件名包含"履历" -> 返回内容到"履历"列表
    情况2：文件名包含"线索" -> 返回内容到"线索"列表
    情况3：其他 -> 如果llm_flag=true，通过LLM判断是否为履历或线索，是则返回对应类型列表
    
    请求体格式：raw JSON
    请求体示例:
    {
        "filenames": ["文件1.doc", "文件2.docx", "文件3.xlsx"],
        "file_contents": ["文件1的内容", "文件2的内容", "文件3的内容"],
        "llm_flag": "false",
        "llm_summarize_flag": "false",
        "length": 1000,
        "delay": 100
    }
    
    Args:
        filenames: 文件名列表（字符串数组）
        file_contents: 文件内容列表（字符串数组，顺序需与filenames一致）
        llm_flag: 是否启用大模型判断文件类型（字符串，默认"false"）
        llm_summarize_flag: 是否启用LLM总结摘要（字符串，true/false，默认"false"）
        length: 总结摘要的长度（整数，默认1000）
        delay: 并发延迟时间（毫秒，整数，默认100）
    
    Returns:
        返回格式：
        {
            "status": "success",
            "履历_count": 2,
            "线索_count": 2,
            "result": [
                {"type": "履历", "content": "content1", "filename": "file1.txt"},
                {"type": "履历", "content": "content2", "filename": "file2.txt"},
                {"type": "线索", "content": "content3", "filename": "file3.txt"},
                {"type": "线索", "content": "content4", "filename": "file4.txt"}
            ]
        }
    """
    # 验证文件数量和内容数量是否一致
    if len(filenames) != len(file_contents):
        raise HTTPException(
            status_code=400,
            detail=f"文件名数量({len(filenames)})与文件内容数量({len(file_contents)})不匹配"
        )
    
    # 返回格式是列表：[{"type": "履历", "content": "..."}, ...]
    result_list = await file_name_judgement(filenames, file_contents, llm_flag, llm_summarize_flag, length, delay)
    
    # 统计各类型的数量
    履历_count = sum(1 for item in result_list if item.get("type") == "履历")
    线索_count = sum(1 for item in result_list if item.get("type") == "线索")
    
    return JSONResponse(content={
        "status": "success",
        "履历_count": 履历_count,
        "线索_count": 线索_count,
        "result": result_list  # 直接返回列表格式
    })


# # method_100：LangGraph可视化接口
# @app.get("/preliminary_review/visualize/")
# async def visualize_preliminary_review_graph(
#         format: Optional[str] = "mermaid"
# ):
#     """
#     可视化初步审查报告的LangGraph流程图
#
#     Args:
#         format: 可视化格式，可选 "mermaid"（默认）、"ascii"、"png"、"pdf"
#
#     Returns:
#         JSONResponse: 包含可视化结果的响应
#     """
#     try:
#         result = visualize_graph(format)
#
#         if format == "mermaid":
#             return JSONResponse(content={
#                 "status": "success",
#                 "format": "mermaid",
#                 "mermaid_code": result,
#                 "message": "可以在支持Mermaid的Markdown编辑器中查看，或使用在线工具：https://mermaid.live/"
#             })
#         elif format == "ascii":
#             return JSONResponse(content={
#                 "status": "success",
#                 "format": "ascii",
#                 "ascii_art": result,
#                 "message": "ASCII格式的流程图，可在终端中查看"
#             })
#         elif format in ["png", "pdf"]:
#             return JSONResponse(content={
#                 "status": "success",
#                 "format": format,
#                 "file_path": result,
#                 "message": f"图形文件已生成: {result}"
#             })
#         else:
#             return JSONResponse(
#                 status_code=400,
#                 content={
#                     "status": "error",
#                     "message": f"不支持的格式: {format}，支持: mermaid, ascii, png, pdf"
#                 }
#             )
#     except Exception as e:
#         log.error(f"可视化失败: {e}", exc_info=True)
#         raise HTTPException(status_code=500, detail=f"可视化失败: {str(e)}")

@app.post("/call_llm/")
async def call_llm_main(
        name: Optional[str] = None,
        system_prompt: Optional[str] = None,
        user_prompt: Optional[str] = None,
        concurrent: Optional[int] = None,
):
    """
    调用大模型接口
    
    Args:
        name: 模型名称（可选，默认"qwen3-14b"）
        system_prompt: 系统提示词（可选）
        user_prompt: 用户输入提示词（可选）
        concurrent: 并发数量（可选，默认为配置文件中的值）
    
    Returns:
        List[str]: 模型返回结果列表
    
    请求体示例:
    {
        "name": "qwen3-14b",
        "system_prompt": "你是一个专业的助手",
        "user_prompt": "请给我讲一个笑话",
        "concurrent": 2
    }
    """
    result = await _run_all_configs_once(
        name=name,
        system_prompt=system_prompt,
        user_prompt=user_prompt,
        concurrent=concurrent
    )
    # 去掉<think>\n\n</think>\n\n
    # result = re.sub(r'<think>.*?</think>', '', result, flags=re.DOTALL).strip()
    return JSONResponse(content={
        "status": "success",
        "result": result or [],
        "count": len(result) if result else 0
    })

class ClueRepeatRequest(BaseModel):
    """线索重复判断请求模型"""
    clue_list: List[str]  # 线索列表（字符串数组）
    key_extract_content: str  # 纯文本格式的信访内容
    name: Optional[str] = None
    system_prompt: Optional[str] = None
    user_prompt: Optional[str] = None
    concurrent: Optional[int] = None

@app.post("/research_judgement_clue_repeat/")
async def clue_repeat_main(request: ClueRepeatRequest):
    """
    判断线索是否重复
    
    Args:
        clue_list: 线索列表（字符串数组），每个元素是一条线索
        key_extract_content: 纯文本格式的信访内容（会自动调用LLM解析为JSON结构）
        name: 模型名称（可选，默认qwen3-14b）
        system_prompt: 自定义系统提示词（可选）
        user_prompt: 自定义用户提示词（可选，可使用{clue_list}和{content_summary}占位符）
        concurrent: 并发数（可选）
    
    请求体示例:
    {
        "clue_list": [
            "知识库线索内容：\"编号\":\"[2025]002\";\"时间\":\"45691\";\"被反映人\":\"张明\";\"被反映单位\":\"无\";\"线索事件\":\"2024年2月至5月期间，霞山分公司经理张明多次在非公务时间驾驶粤G2K113别克商务车前往外地办理私事，包括周末往返霞山市与邻市（距离150公里）探亲，及工作日下班后使用车辆接送亲友；\"",
            "知识库线索内容：\"编号\":\"[2025]001\";\"时间\":\"45690\";\"被反映人\":\"张明\";\"被反映单位\":\"无\";\"线索事件\":\"长期占用霞山分公司粤G2K113别克商务车公车私用；\""
        ],
        "key_extract_content": "接到匿名举报，反映稻国烟草局霞山分公司经理张明存在以下问题：一、公车私用问题。2024年2月至5月期间...",
        "name": "qwen3-14b",
        "system_prompt": "可选的自定义系统提示词",
        "user_prompt": "可选的用户提示词",
        "concurrent": 1
    }
    
    注意：
    1. clue_list 现在是数组格式，每个元素是一条线索的完整内容
    2. key_extract_content 是纯文本，会自动调用LLM解析为结构化JSON
    
    Returns:
        处理后的结果，每个实体添加了repeat_flag字段（true/false/error）
    """
    # 打印输入参数日志（使用repr安全打印，避免特殊字符导致格式化错误）
    log.info("=== 接收到请求参数 ===")
    log.info("clue_list 数量: %d", len(request.clue_list) if request.clue_list else 0)
    if request.clue_list and len(request.clue_list) > 0:
        for idx, clue in enumerate(request.clue_list[:3]):  # 只打印前3条
            log.info("线索%d (前200字符): %s", idx+1, repr(clue[:200]))
        if len(request.clue_list) > 3:
            log.info("... 还有 %d 条线索未显示", len(request.clue_list) - 3)
    log.info("key_extract_content (前500字符): %s", repr(request.key_extract_content[:500]) if request.key_extract_content else "None")
    log.info("key_extract_content 总长度: %d", len(request.key_extract_content) if request.key_extract_content else 0)
    log.info("name: %s", request.name)
    log.info("======================")
    
    # 调用判断函数（会自动处理JSON字符串的清理和解析）
    result = await judgement_clue_repeat(
        clue_list=request.clue_list,
        key_extract_content=request.key_extract_content,  # 直接传递字符串，在judgement_clue_repeat中处理
        name=request.name,
        system_prompt=request.system_prompt,
        user_prompt=request.user_prompt,
        concurrent=request.concurrent
    )
    # 提取res列表，直接返回列表
    res_list = result.get("res", [])
    return JSONResponse(content=res_list)

if __name__ == '__main__':
    sever_host = str(CONFIG.get("KNOWLEDGE_BASES_HOST"))
    server_port = int(CONFIG.get("KNOWLEDGE_BASES_PORT"))
    uvicorn.run(app, host=sever_host, port=server_port)